#include "Python.h"
#include "mathlink.h"
#include <stdio.h>
#include <stdlib.h>


#define PYMLTKFUNC 0
#define PYMLTKINT 1
#define PYMLTKREAL 2
#define PYMLTKSTR 3
#define PYMLTKSYM 4
#define PYMLTKERROR 5

MLENV ep = (MLENV)0;
MLINK lp = (MLINK)0;


static void error( MLINK lp)
{
  if( MLError( lp)){
    fprintf( stderr, "Error detected by MathLink: %s.\n",
	     MLErrorMessage(lp));
  }else{
    fprintf( stderr, "Error detected by this program.\n");
  }
  exit(3);
}

static void deinit( void)
{
  if( ep) {
    MLDeinitialize( ep);
    ep = (MLENV)0;
  }
}


static void closelink( void)
{
  if( lp) {
    MLClose( lp);
    lp = (MLENV)0;
  }
}



static PyObject *
PutFunction(PyObject *self, PyObject *args)
{
  int n;
  char *func;

  if (!PyArg_ParseTuple(args, "si", &func, &n))
    {
      fprintf(stderr,"PutFunction: could not parse tuple\n");
      Py_INCREF(Py_None);
      return Py_None;    
    } 
  
  MLPutFunction( lp, func, n);
  if (MLError( lp)) error( lp);

  Py_INCREF(Py_None);
  return Py_None;    
}

static PyObject *
PutSymbol(PyObject *self, PyObject *args)
{
  char *symbol;

  if (!PyArg_ParseTuple(args, "s", &symbol))
    {
      fprintf(stderr,"PutSymbol: could not parse tuple\n");
      Py_INCREF(Py_None);
      return Py_None;    
    } 

  MLPutSymbol(lp, symbol);
  if (MLError( lp)) error( lp);

  Py_INCREF(Py_None);
  return Py_None;    
}

static PyObject *
PutString(PyObject *self, PyObject *args)
{
  char *string;

  if (!PyArg_ParseTuple(args, "s", &string))
    {
      fprintf(stderr,"PutString: could not parse tuple\n");
      Py_INCREF(Py_None);
      return Py_None;    
    } 

  MLPutString(lp, string);
  if (MLError( lp)) error( lp);

  Py_INCREF(Py_None);
  return Py_None;    
}


static PyObject *
PutInteger(PyObject *self, PyObject *args)
{
  int integer;

  if (!PyArg_ParseTuple(args, "i", &integer))
    {
      fprintf(stderr,"PutInteger: could not parse tuple\n");
      Py_INCREF(Py_None);
      return Py_None;    
    } 

  MLPutInteger(lp, integer);
  if (MLError( lp)) error( lp);

  Py_INCREF(Py_None);
  return Py_None;    
}

static PyObject *
PutReal(PyObject *self, PyObject *args)
{
  double real;

  if (!PyArg_ParseTuple(args, "d", &real))
    {
      fprintf(stderr,"PutReal: could not parse tuple\n");
      Py_INCREF(Py_None);
      return Py_None;    
    } 

  MLPutReal(lp, real);
  if (MLError( lp)) error( lp);

  Py_INCREF(Py_None);
  return Py_None;    
}

static PyObject *
PutIntegerList(PyObject *self, PyObject *args)
{
  PyObject *PyIntegerList;
  int *integerlist, length, i;

  if (!PyArg_ParseTuple(args, "O", &PyIntegerList))
    {
      fprintf(stderr,"PutIntegerList: could not parse tuple\n");
      Py_INCREF(Py_None);
      return Py_None;    
    } 

  length = PySequence_Length(PyIntegerList);
  integerlist = (int *) malloc(sizeof(int) * length);
  for (i=0; i< length; i++) {
    integerlist[i] = PyInt_AsLong(PySequence_GetItem(PyIntegerList, i));
  }

  MLPutIntegerList(lp, integerlist, length);
  if (MLError( lp)) error( lp);

  Py_INCREF(Py_None);
  return Py_None;    
}

static PyObject *
PutRealList(PyObject *self, PyObject *args)
{
  PyObject *PyRealList;
  int length, i;
  double *reallist;

  if (!PyArg_ParseTuple(args, "O", &PyRealList))
    {
      fprintf(stderr,"PutRealList: could not parse tuple\n");
      Py_INCREF(Py_None);
      return Py_None;    
    } 

  length = PySequence_Length(PyRealList);
  reallist = (double *) malloc(sizeof(double) * length);
  for (i=0; i< length; i++) {
    reallist[i] = PyFloat_AsDouble(PySequence_GetItem(PyRealList, i));
  }

  MLPutRealList(lp, reallist, length);
  if (MLError( lp)) error( lp);

  Py_INCREF(Py_None);
  return Py_None;    
}


static PyObject *
GetSymbol(PyObject *self, PyObject *args)
{
  const char *symbol;
  PyObject *PySymbol;

  MLGetSymbol(lp, &symbol);
  if (MLError( lp)) error( lp);
  
  PySymbol = Py_BuildValue("s", symbol);
  MLDisownSymbol(lp, symbol);

  return PySymbol;    
}


/* I suspect this is not a correct implementation of GetByteString.
If you encounter any errors, please let me know!*/
static PyObject *
GetByteString(PyObject *self, PyObject *args)
{

  const unsigned char *string;
  long missing;
  int len;
  PyObject *PyString;

  missing = 33;

  MLGetByteString(lp, &string, &len, missing);
  if (MLError( lp)) error( lp);
  /*   printf("GetByteString: \"%s\"\n", (char *) string);
       printf("GetByteString(%ld)\n", len); */
  
  PyString = Py_BuildValue("s#", (char *)string, (int)len);
  MLDisownString(lp, (const char *)string);

  return PyString;    
}

static PyObject *
GetString(PyObject *self, PyObject *args)
{
  const char *string;
  PyObject *PyString;

  MLGetString(lp, &string);
  if (MLError( lp)) error( lp);
  
  PyString = Py_BuildValue("s", string);
  MLDisownString(lp, string);

  return PyString;    
}

static PyObject *
GetInteger(PyObject *self, PyObject *args)
{
  int Integer;
  PyObject *PyInteger;

  MLGetInteger(lp, &Integer);
  if (MLError( lp)) error( lp);
  
  PyInteger = Py_BuildValue("i", Integer);

  return PyInteger;    
}

static PyObject *
GetReal(PyObject *self, PyObject *args)
{
  double Real;
  PyObject *PyReal;

  MLGetReal(lp, &Real);
  if (MLError( lp)) error( lp);
  
  PyReal = Py_BuildValue("d", Real);

  return PyReal;    
}

static PyObject *
GetFunction(PyObject *self, PyObject *args)
{
  int len;
  
  const char *Function;
  PyObject *PyFunction;

  MLGetFunction(lp, &Function, &len);
  if (MLError( lp)) error( lp);
  
  PyFunction = Py_BuildValue("si", Function, len);
  MLDisownSymbol(lp, Function);

  return PyFunction;    
}

static PyObject *
GetNext(PyObject *self, PyObject *args)
{
  int next;
  
  PyObject *PyNext;

  next = MLGetNext(lp);
  if (MLError( lp)) error( lp);

  switch(next) {
  case MLTKFUNC:
    PyNext = Py_BuildValue("i", PYMLTKFUNC);
    break;
  case MLTKSYM:
    PyNext = Py_BuildValue("i", PYMLTKSYM);
    break;
  case MLTKSTR:
    PyNext = Py_BuildValue("i", PYMLTKSTR);
    break;
  case MLTKREAL:
    PyNext = Py_BuildValue("i", PYMLTKREAL);
    break;
  case MLTKINT:
    PyNext = Py_BuildValue("i", PYMLTKINT);
    break;
  case MLTKERROR:
    PyNext = Py_BuildValue("i", PYMLTKERROR);
    break;
  default:
    printf("Invalid packet value in GetNext: %d\n",next);
    return NULL;
    break;
  }
  return PyNext;    
}

static PyObject *
NextPacket(PyObject *self, PyObject *args)
{
  int next;
  
  PyObject *PyNext;

  next = MLNextPacket(lp);
  if (MLError( lp)) error( lp);
  
  PyNext = Py_BuildValue("i", next);

  return PyNext;    
}

static PyObject *
NewPacket(PyObject *self, PyObject *args)
{
  MLNewPacket(lp);
  if (MLError( lp)) error( lp);
  
  Py_INCREF(Py_None);
  return Py_None;
    
}

static PyObject *
init(PyObject *self, PyObject *args)
{
  /* I had trouble using argc and argv unless I renamed them.
  possibly unnecessary? */
  int argc1=3;
  char *argv1[3];
  int err;
  if (!PyArg_ParseTuple(args, "sss", &argv1[0], &argv1[1], &argv1[2]))
    {
      fprintf(stderr,"init: could not parse tuple\n");
      Py_INCREF(Py_None);
      return Py_None;    
    } 

  ep =  MLInitialize( (MLParametersPointer)0);
  if( ep == (MLENV)0) exit(1);
  atexit( deinit);

  lp = MLOpenArgv( ep, argv1, argv1 + argc1, &err);
  if(lp == (MLINK)0) exit(2);
  atexit( closelink);


  Py_INCREF(Py_None);
  return Py_None;    
}


static PyObject *
finish(PyObject *self, PyObject *args)
{
  MLPutFunction( lp, "Exit", 0);
  MLEndPacket(lp);
  /* put these here to shut down kernel early */
  closelink();
  deinit();
  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject *
EndPacket(PyObject *self, PyObject *args)
{
  MLEndPacket(lp);
  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject *
Flush(PyObject *self, PyObject *args)
{
  MLFlush(lp);
  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject *
Ready(PyObject *self, PyObject *args)
{
  PyObject *PyReady;
  int ready;
  ready = MLReady(lp);
  PyReady = Py_BuildValue("i", ready);
  return PyReady;
}

static PyMethodDef Methods[] =
{
  {"init",  init, 1},
  {"finish",  finish, 1},
  {"PutFunction",  PutFunction, 1},
  {"PutSymbol",  PutSymbol, 1},
  {"PutString",  PutString, 1},
  {"PutInteger",  PutInteger, 1},
  {"PutReal",  PutReal, 1},
  {"PutIntegerList",  PutIntegerList, 1},
  {"PutRealList",  PutRealList, 1},
  {"GetNext",  GetNext, 1},
  {"GetSymbol",  GetSymbol, 1},
  {"GetString",  GetString, 1},
  {"GetByteString",  GetByteString, 1},
  {"GetInteger",  GetInteger, 1},
  {"GetReal",  GetReal, 1},
  {"GetFunction",  GetFunction, 1},
  {"NextPacket",  NextPacket, 1},
  {"NewPacket",  NewPacket, 1},
  {"EndPacket",  EndPacket, 1},
  {"Flush",  Flush, 1},
  {"Ready",  Ready, 1},
  {NULL,      NULL}};

void
init_mathlink(void)
{
  PyObject *m, *d;
  PyObject *MLTKError;
  
  m= Py_InitModule("_mathlink", Methods);

  d = PyModule_GetDict(m);
  MLTKError = PyString_FromString("MLTKError");
   PyDict_SetItemString(d, "MLTKError", MLTKError);  

  if (PyErr_Occurred())
    Py_FatalError("can't initialize module _mathlink");

}
